package com.tongtong.middleware.blockchain.client.web3j;

import java.io.IOException;
import java.math.BigInteger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Response;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthTransaction;
import org.web3j.utils.Numeric;

import com.tongtong.middleware.blockchain.client.bsn.BSNService;
import com.tongtong.middleware.blockchain.entity.TblMiddlewareLog;
import com.tongtong.middleware.blockchain.mapper.TblMiddlewareLogMapper;
import com.tongtong.middleware.blockchain.service.MiddlewareLogService;
import com.tongtong.middleware.vo.params.bsn.RechargeRequest;
import com.tongtong.middleware.vo.response.EstimateGasRes;
import com.tongtong.middleware.vo.response.SignTxRes;
import com.tongtong.middleware.vo.response.TransactionMergeData;
import com.tongtong.middleware.vo.response.bsn.RechargeRes;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @author wangqi
 */
@Service
@Slf4j
public class WEB3JService {
	public final String managerAddr = "0x3b5Be4c33249458534fcaa38Aa38B194E1d0EFC4";
	@Autowired
	WEB3JConfig config;
	@Autowired
	BSNService bsnService;
	@Autowired
	TblMiddlewareLogMapper tblMiddlewareLogMapper;
	@Autowired
	MiddlewareLogService middlewareLogService;

	/**
	 * 获取链ID
	 */
	public BigInteger getChainId() throws IOException {
		return config.conn().ethChainId().send().getChainId();
	}

	/**
	 * 根据区块高度获取区块信息
	 */
	public EthBlock getBlockByNumber(BigInteger number) throws IOException {
		return config.conn().ethGetBlockByNumber(DefaultBlockParameter.valueOf(number), true).send();
	}

	/**
	 * 获取最新区块高度
	 */
	public BigInteger blockNumber() throws IOException {
		return config.conn().ethBlockNumber().send().getBlockNumber();
	}

	/**
	 * 获取账户nonce值
	 */
	public BigInteger getTransactionCount(String account) throws IOException {
		return config.conn().ethGetTransactionCount(account, DefaultBlockParameterName.PENDING).send().getTransactionCount();
	}

	/**
	 * 获取建议gas price 值
	 */
	public BigInteger gasPrice() throws IOException {
		return config.conn().ethGasPrice().send().getGasPrice();
	}

	/**
	 * 预估交易gas值
	 */
	public EstimateGasRes estimateGas(String from, String to, String input) throws IOException {
		Transaction tx = Transaction.createEthCallTransaction(from, to, input);
		EthEstimateGas estimateGas = config.conn().ethEstimateGas(tx).send();
		EstimateGasRes estimateGasRes = new EstimateGasRes();
		if (estimateGas.hasError()) {
			estimateGasRes.setGasUsed(BigInteger.ZERO);
			estimateGasRes.setMsg(estimateGas.getError().getMessage());
		} else {
			estimateGasRes.setGasUsed(estimateGas.getAmountUsed());
			estimateGasRes.setMsg("");
		}
		return estimateGasRes;
	}

	/**
	 * 获取用户余额
	 */
	public BigInteger getBalance(String account) throws IOException {
		return config.conn().ethGetBalance(account, DefaultBlockParameterName.PENDING).send().getBalance();
	}

	/**
	 * 根据交易hash获取交易信息
	 */
	public EthTransaction getTransactionByHash(String hash) throws IOException {
		return config.conn().ethGetTransactionByHash(hash).send();
	}

	/**
	 * 根据交易hash获取智能合约信息
	 */
	public EthGetTransactionReceipt getTransactionReceipt(String hash) throws IOException {
		return config.conn().ethGetTransactionReceipt(hash).send();
	}

	/**
	 * 根据交易hash获取交易全部信息
	 */
	public TransactionMergeData getTransactionInfo(String hash) throws IOException {
		TransactionMergeData res = new TransactionMergeData();
		// 分别获取两个接口的数据
		EthTransaction hashRes = getTransactionByHash(hash);
		EthGetTransactionReceipt receiptRes = getTransactionReceipt(hash);
		// 获取两个接口数据写入到新结构体
		// 交易没上链时区块高度时null，需要判断一下
		if (hashRes.getResult().getBlockNumber() != null) {
			res.setBlockNumber(hashRes.getResult().getBlockNumber());
			res.setFrom(hashRes.getResult().getFrom());
			res.setGas(receiptRes.getResult().getGasUsed());
			res.setGasPrice(hashRes.getResult().getGasPrice());
			res.setHash(hashRes.getResult().getHash());
			res.setInput(hashRes.getResult().getInput());
			res.setNonce(hashRes.getResult().getNonce());
			res.setTo(hashRes.getResult().getTo());
			res.setTransactionIndex(hashRes.getResult().getTransactionIndex());
			res.setValue(hashRes.getResult().getValue());
			res.setStatus(receiptRes.getResult().getStatus());
			res.setGasUsed(receiptRes.getResult().getGasUsed());
			res.setLogs(receiptRes.getResult().getLogs());
		}
		return res;
	}

	/**
	 * 智能合约查询
	 */
	public String call(String from, String to, String input) throws IOException {
		Transaction tx = Transaction.createEthCallTransaction(from, to, input);
		return config.conn().ethCall(tx, DefaultBlockParameterName.PENDING).send().getValue();
	}

	/**
	 * 发送链上交易
	 */
	public SignTxRes sendRawTransaction(byte[] signedMessage) {
		String singed = Numeric.toHexString(signedMessage);
		SignTxRes res;
		try {
			Response<String> sendRawTransactionRes = config.conn().ethSendRawTransaction(singed).send();
			if (sendRawTransactionRes.getResult() == null) {
				res = new SignTxRes("", sendRawTransactionRes.getError().getMessage());
			} else {
				res = new SignTxRes(sendRawTransactionRes.getResult(), "");
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return res;
	}

	public SignTxRes signTx(String toAddress, String input, String priKey) throws IOException {
		// 通过私钥创建Credentials对象
		ECKeyPair keyPair = ECKeyPair.create(Numeric.toBigInt(priKey));
		Credentials credentials = Credentials.create(keyPair);
		// 创建交易对象
		RawTransaction rawTransaction = RawTransaction
				.createTransaction(getTransactionCount(credentials.getAddress()), new BigInteger("1000000000"), new BigInteger(
						"500000"), toAddress, BigInteger.ZERO, input);
		// 交易先预执行
		EstimateGasRes estimateGasRes = estimateGas(credentials.getAddress(), toAddress, input);
		if (estimateGasRes.getGasUsed().intValue() == 0) {
			log.error("【失败】signTx 预执行 toAddress:{} input:{} err {}", toAddress, input, estimateGasRes.getMsg());
			TblMiddlewareLog packEntity = middlewareLogService
					.packEntity("aliyun", "signTx", input, JSONUtil.toJsonStr(estimateGasRes), estimateGasRes.getMsg(), "", "");
			tblMiddlewareLogMapper.insert(packEntity);
			return new SignTxRes("", estimateGasRes.getMsg());
		}
		// 对交易进行签名
		byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, getChainId().longValue(), credentials);
		SignTxRes res = sendRawTransaction(signedMessage);
		if ("insufficient funds for gas * price + value".equals(res.getErrMsg()) && credentials.getAddress().equals(managerAddr)) {
			log.error("【失败】signTx 触发余额不足给管理员账户充值");
			RechargeRequest rechargeRequest = new RechargeRequest();
			rechargeRequest.setOpbChainId(4);
			rechargeRequest.setOpbChainClientAddress(managerAddr);
			rechargeRequest.setHashRateValue(200);
			RechargeRes rechargeRes = bsnService.recharge(rechargeRequest);
			if (rechargeRes.getErrorLogCode() == null) {
				// 间歇10秒
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				log.info("【成功】signTx 触发余额不足给管理员账户充值能量值为 {}", rechargeRes.getData().getEnergyValue());
			} else {
				log.error("【失败】signTx 触发余额不足给管理员账户充值 err {}", rechargeRes.getErrorLogMessage());
			}
		}
		return res;
	}
}
